<?php


namespace EchoPHP\Validator;

use EchoPHP\Support\Arr;
use InvalidArgumentException;

class Validator {

    use ValidateAttribute;

    /**
     * 表单数据
     * @var array
     */
    protected $data = [];

    /**
     * 校验规则
     * @var array
     */
    protected $rules = [];

    protected $messages = [];

    protected $errors = [];

    protected $bail = false;


    public function __construct(array $rules = [], array $messages = [], bool $bail = false) {
        $this->setRules($rules);
        $this->setMessages($messages);
        $this->setBail($bail);
    }

    /**
     * 校验方法
     * @param array $data
     * @return bool
     */
    public function validate(array $data) {
        $this->setData($data);
        $this->errors = [];
        $validateResult = true;
        foreach ($this->rules as $field => $rule) {
            $ruleItems = $this->parseRule($rule);
            if (strpos($field, '|') > 0) {
                list($field, $title) = explode('|', $field, 2);
            } else {
                $field = $field;
                $title = $field;
            }
            $result = $this->validateItem($field, $title, $ruleItems);
            if (!$result && $this->bail) {
                return false;
            }
            $validateResult = $validateResult && $result;
        }
        return $validateResult;

    }

    /**
     * 单项校验
     * @param $field
     * @param $title
     * @param $ruleItems
     * @return bool
     */
    protected function validateItem($field, $title, $ruleItems) {
        $value = $this->getData($field);
        foreach ($ruleItems as $ruleItem) {
            $ruleName = $ruleItem[0];
            $ruleParameters = $ruleItem[1] ?? null;
            if ($ruleName instanceof Rule) {
                $result = $ruleName->validate($field, $title, $value, $this->data);
                if ($result === false) {
                    $this->errors[$field] = $ruleName->message();
                    return false;
                }
            } else {
                $validaMethod = 'validate' . ucfirst($ruleName);
                if (!method_exists($this, $validaMethod)) {
                    throw new InvalidArgumentException('校验方法[' . $ruleName . ']未定义');
                }

                //如果传入空值且不是required必填验证，则无需使用校验
                if (0 === strpos($ruleName, 'required') || (!is_null($value) && '' !== $value)) {
                    $result = $this->{$validaMethod}($field, $value, $ruleParameters);
                } else {
                    $result = true;
                }

                if ($result === false) {
                    $this->errors[$field] = $this->getRuleMsg($field, $title, $ruleName, $ruleParameters);

                    return false;
                }
            }
        }

        return true;
    }

    /**
     * 获取校验规则提示信息
     * @param $attribute
     * @param $attributeTitle
     * @param $rule
     * @param $parameters
     * @return mixed|string
     */
    protected function getRuleMsg($attribute, $attributeTitle, $rule, $parameters) {
        if (!is_null($this->getMessages($attribute . '.' . $rule))) {
            $message = $this->getMessages($attribute . '.' . $rule);
        } elseif (!is_null($this->getMessages($attribute))) {
            $message = $this->getMessages($attribute);
        } elseif (isset(self::$typeMsg[$rule])) {
            $message = self::$typeMsg[$rule];
        } else {
            $message = $attributeTitle . '验证不通过';
        }
        $parameters = is_null($parameters) ? '' : $parameters;
        if (is_array($parameters)) {
            $parameters = implode(', ', $parameters);
        }

        if (is_string($message) && is_scalar($parameters) && false !== strpos($message, ':')) {
            $message = str_replace(
                [':attribute', ':rule'],
                [$attributeTitle, (string)$parameters],
                $message);
        }
        return $message;
    }

    /**
     * 获取错误信息
     * @return array
     */
    public function getErrors() {
        return $this->errors;
    }

    /**
     * 设置待校验数据
     * @param array $data
     */
    public function setData(array $data) {
        $this->data = $data;
    }

    /**
     * 获取数据值
     * @param $attribute
     * @return mixed
     */
    protected function getData($attribute) {
        return Arr::get($this->data, $attribute);
    }

    /**
     * 设置验证规则
     * @param array $rules
     */
    public function setRules(array $rules) {
        $this->rules = $rules;
    }

    /**
     * 解析规则
     * @param $rule
     * @return array
     */
    protected function parseRule($rule) {
        $items = [];
        // 'require', 'min' => 10, 'maxLength' => 20, 'in' => '1,2,3,4'
        foreach ($rule as $key => $item) {
            if (is_numeric($key)) {
                //数字下标，无参数验证方法
                $items[] = [$item];
            } else {
                //带参数验证
                $items[] = [$key, $item];
            }
        }
        return $items;
    }

    /**
     * 设置是否在出现第一个校验不通过时停止后续校验
     * @param bool $bail
     */
    public function setBail(bool $bail = false) {
        $this->bail = $bail;
    }

    /**
     * 设置错误提示信息
     * @param array $messages
     */
    public function setMessages(array $messages = []) {
        $this->messages = $messages;
    }

    /**
     * 获取错误提示信息
     * @param $attribute
     * @return mixed
     */
    public function getMessages($attribute) {
        return Arr::get($this->messages, $attribute);
    }


}